package momo.multitree.util;

import java.io.FileReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.Random;
import java.util.Vector;

import momo.multitree.structure.*;
import momo.pagri.util.ZipfDistribution;
import momo.pagri.util.ZipfDistributionAdapter;



public class GraphGenerator
{
	static final int SITE[] = {2, 3, 4};
	static final int MAX_PEER_AND_LEAF = 10;
	static final int MIN_PEER_AND_LEAF = 2;
	static final int MIN_LAT_LOW = 5;
	static final int MAX_LAT_LOW = 20;
	static final int MIN_LAT_HIGH = 100;
	static final int MAX_LAT_HIGH = 500;
	
	static final int MIN_COST_LOW = 5;
	static final int MAX_COST_LOW = 20;
	static final int MIN_COST_HIGH = 100;
	static final int MAX_COST_HIGH = 500;
	
//	static final int MIN_COST_HIGH = 1;
//	static final int MAX_COST_HIGH = 500;

	
	static final double NON_FIREWALLED = 0.2d;
	private final long RANDOM_SEED = 13421;
	
	private Random rand;
	private long seed;
	
	
	public GraphGenerator()
	{
		seed = RANDOM_SEED;
		rand = new Random(seed);
	}
	
	public GraphGenerator(long seed)
	{
		this.seed = seed;
		rand = new Random(seed);
	}
	
	public void generateGraph(int desireNoOfNode, double nonFW, boolean isSymmetric, String filename, boolean generateLeaf)
	{
		try
		{
			String data = generateGraph(desireNoOfNode, nonFW, isSymmetric, generateLeaf);
			PrintWriter out = new PrintWriter(filename);
			out.println(data);
			out.flush();
			out.close();
		}catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public String generateGraph(int desireNoOfNode, double nonFW, boolean isSymmetric, boolean generateLeaf)
	{
		StringBuffer nodeBuffer = new StringBuffer();
		StringBuffer edgeBuffer = new StringBuffer();
		
		int noOfNode = desireNoOfNode;
		int noOfEdge = 0;
		int noOfSite = getNoOfSite();
		Vector<Node> site[] = new Vector[noOfSite];
		Vector<Node> peers[] = new Vector[noOfSite] ;
		Vector<Node> nonFWPeers[] = new Vector[noOfSite] ;
		boolean hasNonFWPeers = false;
		double workload[] = this.getWorkload(0.05, desireNoOfNode);
		double stability[] = this.getStability(desireNoOfNode);
		
		for(int i=0; i<noOfSite; i++)
		{
			site[i] = new Vector<Node>();
			peers[i] = new Vector<Node>();
			nonFWPeers[i] = new Vector<Node>();
		}
		
		//output no of Nodes to buffer
		nodeBuffer.append(noOfNode+"\n");
		// All Nodes
		for(int i=0; i<noOfSite; i++)
		{
			Node node = new Node(""+i, workload[i], stability[i]);
			site[i].add(node);
			//output to buffer
			String extraInfo = "Site ID: " + i; 
			nodeBuffer.append(this.toNodeString(node, extraInfo));
		}
		
		for(int i=noOfSite; i<desireNoOfNode; i++)
		{
			Node node = new Node(""+i, workload[i], stability[i]);
			int siteNo = getRandNo(0, noOfSite-1); 
			site[siteNo].add(node);
			//output to buffer
			String extraInfo = "Site ID: " + siteNo; 
			nodeBuffer.append(this.toNodeString(node, extraInfo));
		}
		
		//Edges of each site
		for(int i=0; i<noOfSite; i++)
		{
			int noOfPeerAndLeaf = site[i].size();
			int noOfPeer = 0;
			int noOfLeaf = 0;
			if (generateLeaf)
				noOfPeer = getRandNo(1, noOfPeerAndLeaf);
			else
				noOfPeer = noOfPeerAndLeaf;
			noOfLeaf = noOfPeerAndLeaf - noOfPeer;
			
			// Peer Edges of LAN
			for(int j=0; j<noOfPeer; j++)
			{
				Node node = site[i].get(j);
				
				if ( isFireWalled(nonFW) )
				{
					nonFWPeers[i].add(node);
					hasNonFWPeers = true;
				}
				peers[i].add(node);
					
				for(int k=j+1; k<noOfPeer; k++)
				{
					Edge edge;
					if ( isSymmetric )
					{
						double cost = getCost(true);
						double lat = getLatency(true);
						edge = new Edge(""+noOfEdge, node, site[i].get(k), cost, cost, lat, lat);
					}else
					{
						edge = new Edge(""+noOfEdge, node, site[i].get(k), 
								getCost(true), getCost(true), 
								getLatency(true), getLatency(true));
					}
					//output to buffer
					edgeBuffer.append(this.toEdgeString(edge, "Peer Edges of Site " + i));
					noOfEdge ++;
				}
			}
			
			// Leaf Edges of LAN
			for(int j=noOfPeer; j<noOfPeerAndLeaf; j++)
			{
				Edge edge;
				if ( isSymmetric )
				{
					double cost = getCost(true);
					double lat = getLatency(true);
					edge = new Edge(""+noOfEdge, site[i].get(j), site[i].get(zipf(0.05, noOfPeer)), 
							cost, cost, 
							lat, lat);
				}else
				{
					edge = new Edge(""+noOfEdge, site[i].get(j), site[i].get(zipf(0.05, noOfPeer)), 
							getCost(true), getCost(true), 
							getLatency(true), getLatency(true));
				} 
				// output to buffer
				edgeBuffer.append(this.toEdgeString(edge, "Leaf Edges of Site " + i));
				noOfEdge ++;
			}
		}
		
		//to solve disjoint graph
		if ( !hasNonFWPeers )
		{
			int siteId = getRandNo(0, noOfSite-1);
			int noOfNonFWPeer = getRandNo(1, peers[siteId].size());
			for(int i=0; i<noOfNonFWPeer; i++)
				nonFWPeers[siteId].add(peers[siteId].get(i));
		}
		
		// WAN Edges
		for(int i=0; i<noOfSite; i++)
		{
			for(int j=0; j<noOfSite; j++)
			{
				if ( i == j )
					continue;
				for(int m=0; m<nonFWPeers[i].size(); m++)
				{
					for(int n=0; n<peers[j].size(); n++)
					{
						Edge edge;
						if ( isSymmetric )
						{
							double cost = getCost(false);
							double lat = getLatency(false);
							edge = new Edge(""+noOfEdge, nonFWPeers[i].get(m), peers[j].get(n), 
								cost, cost, 
								lat, lat);
						}else
						{
							edge = new Edge(""+noOfEdge, nonFWPeers[i].get(m), peers[j].get(n), 
									getCost(false), getCost(false), 
									getLatency(false), getLatency(false));
						}
						//output to buffer
						edgeBuffer.append(this.toEdgeString(edge, "WAN Edges of Site"));
						noOfEdge ++;
					}
				}
			}
			//Removes duplicates
			for(int k=0; k<nonFWPeers[i].size(); k++)
				peers[i].remove(nonFWPeers[i].get(k));
		}
		
		return nodeBuffer.toString()+noOfEdge+"\n"+edgeBuffer.toString()+"#Seed No: " + seed;
	}
	
	public double[] getWorkload(double s, int n)
	{
		double workload[] = new double[n];
		
		for(int i=0; i<n; i++)
			workload[i] = 0d;
		
		for(int i=0; i<n; i++)
			workload[zipf(s, n)] ++;
		
		for(int i=0; i<n; i++)
			workload[i] = workload[i] / n;
		
		return workload;
	}
	
	public int zipf(double s, int n)
	{
		int orgIndex = 0;
		try
		{
			ZipfDistribution zipf = new ZipfDistribution(s, n);
			ZipfDistributionAdapter orgDistribution = new ZipfDistributionAdapter(zipf);
			int orgCount = n;
			double orgRand = rand.nextDouble();
			orgIndex = (int)Math.floor(Math.abs(orgDistribution.inverseCumulativeProbability(orgRand)));
			if (orgIndex >= orgCount)
				orgIndex = orgCount - 1;
		}catch(Exception e)
		{
			e.printStackTrace();
		}
		return orgIndex;
	}
	
	public double[] getStability(int length)
	{
		double stability[] = new double[length];
		
		for(int i=0; i<stability.length; i++)
			stability[i] = rand.nextDouble();
		
		return stability;
	}
	
	public double getLatency(boolean isLocal)
	{
		if ( isLocal )
			return getRandNo(MIN_LAT_LOW, MAX_LAT_LOW);
		else
			return getRandNo(MIN_LAT_HIGH, MAX_LAT_HIGH);
	}
	
	public double getCost(boolean isLocal)
	{
		if ( isLocal )
			return 0d;
		else
			return getRandNo(MIN_COST_HIGH, MAX_COST_HIGH);
	}
	
	public boolean isFireWalled(double nonFW)
	{
		double count = rand.nextDouble();
		if ( count <= nonFW )
			return false;
		return true;
	}
	
	public int getNoOfSite()
	{
		return SITE[getRandNo(0, SITE.length-1)];
	}
	
	public int getRandNo(int low, int high)
	{
		return rand.nextInt(high-low+1) + low;
	}
	
	public String toNodeString(Node node, String extraInfo)
	{
		return node.getId() + ", " + 
				node.getWorkloadRate() + ", " + 
				node.getStability()+ ", " +
				extraInfo + "\n";
	}
	
	public String toEdgeString(Edge e, String extraInfo)
	{
		Node head = e.getHeadNode();
		Node tail = e.getTailNode();
		return e.getId() + ", " +
				head.getId()+ ", " +
				tail.getId() + ", " +
				e.getLatency(head) + ", " +
				e.getLatency(tail) + ", " +
				e.getCost(head) + ", " +
				e.getCost(tail) + ", " +
				extraInfo + "\n";
	}
	
	public static void main(String args[])
	{
		int sizePerRound = 100;
		double nonFW = 0.0;
//		double nonFW = GraphGenerator.NON_FIREWALLED;
		
//		int sizes[] = {5, 6, 7, 8, 9, 10, 12, 15, 20, 25, 30};
//		long seeds[] = {568741230, 798415841, 98456210, 1234567890, 945241321, 348978297, 478965431, 98513745, 15730523, 68745125, 345792154};
		
		int sizes[] = {5, 6, 7, 8, 9};
		long seeds[] = {568741230, 798415841, 98456210, 1234567890, 945241321};
								
		for(int i=0; i<sizes.length; i++)
		{
			int size = sizes[i];
			long seed = seeds[i];
			
			Random rand = new Random(seed);
			for(int j=0; j<sizePerRound; j++)
			{
				long curSeed = Math.abs(rand.nextLong());
				GraphGenerator app = new GraphGenerator(curSeed);
//				app.generateGraph(size, false, "dataset/"+size+"_node_dataset_"+(j+1)+".txt");
//				app.generateGraph(size, true, "dataset/"+size+"_node_symmetric_noleaf_dataset_"+(j+1)+".txt", false);
				app.generateGraph(size, nonFW, true, "dataset/"+size+"_node_symmetric_noleaf_fw_"+nonFW+"_dataset_"+(j+1)+".txt", false);
			}
		}
		
	}
}//end of class GraphGenerator
